#!/usr/bin/env python

from gabor import gabor_filter
from scipy import signal
from math import pi
import numpy, scipy

# get a list of kernels with specified configs
def get_kernels(size,env_width,env_height,rotations,frequencies):
    return [gabor_filter(
                    size,
                    size,
                    envelope_rotation = rotation,
                    sinusoid_frequency = frequency,
                    sinusoid_rotation = rotation,
                    envelope_height = env_width,
                    envelope_width = env_height,
                    envelope_peak_x = size/2.0,
                    envelope_peak_y = size/2.0,
                    sinusoid_offset = 0
                ) for rotation in rotations for frequency in frequencies]

# self-explainatory
def convolve(image, kernel):
    image,kernel = map(numpy.float32,(image,kernel))
    return signal.fftconvolve(image, kernel, mode='same')

# generate a vector of response images by convolving the photo with multiple kernels
def vector(photo,kernels):
    return [convolve(photo,kernel) for kernel in kernels]

# calculate distance between two vectors in squared-euclidean metrics
def vector_distance(a,b):
    a,b = map(numpy.float32,(a,b))
    return float(numpy.sum((a-b)**2))

# yay!
def recognize_faces(templates,photos):
    # some configuration
    width = 64
    height = 32
    rotations = [float(k)*pi/5.0 for k in xrange(1,6)]
    frequencies = [1.0/24.0, 1.0/48.0]
    
    # generating kernels for later use
    print 'Generating filters...'
    kernels = get_kernels(max(width,height),width/4,height/4,rotations,frequencies)
    
    # some helper functions, not really necessary
    def get_vector((name,image)):
        return (name,vector(image,kernels))
    def get_vectors((name,images)):
        return (name,[vector(image,kernels) for image in images])
    def assess((person_name, person_templates, photo_name, photo_vector)):
        result = 0.0
        for template_vector in person_templates:
            result += vector_distance(template_vector,photo_vector)
        result /= len(person_templates)
        return (person_name, photo_name, result)

    # generating vectors (~10 response images) by convolving input images with the kernels
    print 'Processing templates...'
    templates = map(get_vectors,templates)
    print 'Processing photos...'
    photos = map(get_vector,photos)

    # query is a tuple (person_name, person_templates, photo_name, photo_vector)
    # where person_templates is a list of vectors generated for each template image for that person
    queries = [(person_name, person_templates, photo_name, photo_vector) for (person_name, person_templates) in templates for (photo_name, photo_vector) in photos]
    # the result for a query is the average distance between the photo_vector and template vectors
    print 'Calculating distances...'
    assessments = map(assess,queries)

    # now, for each photo, find the template which matches best (i.e. distance is smallest)
    print 'Finding best matches...'
    result = dict()
    for (person_name, photo_name, value) in assessments:
        if photo_name in result:
            (old_person_name, old_value) = result[photo_name]
            if old_value > value:
                result[photo_name] = (person_name, value)
        else:
            result[photo_name] = (person_name, value)
    for photo_name in result:
        result[photo_name] = result[photo_name][0]

    return result
